
package com.jf.cloud.multishop.service.impl;

import com.jf.cloud.api.auth.feign.AccountFeignClient;
import com.jf.cloud.api.auth.vo.AuthAccountVO;
import com.jf.cloud.api.biz.feign.NotifyFeignClient;
import com.jf.cloud.api.leaf.feign.SegmentFeignClient;
import com.jf.cloud.api.multishop.vo.ShopDetailVO;
import com.jf.cloud.api.platform.feign.ConfigFeignClient;
import com.jf.cloud.api.platform.vo.SysConfigApiVO;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.constant.DistributedIdKey;
import com.jf.cloud.common.constant.SendTypeEnum;
import com.jf.cloud.common.database.dto.PageDTO;
import com.jf.cloud.common.database.util.PageUtil;
import com.jf.cloud.common.database.vo.PageVO;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.common.security.AuthUserContext;
import com.jf.cloud.common.util.DateUtils;
import com.jf.cloud.common.util.Json;
import com.jf.cloud.multishop.bo.mongo.MongoShopWalletLogBO;
import com.jf.cloud.multishop.constant.*;
import com.jf.cloud.multishop.dto.ShopWithdrawCashConfigDto;
import com.jf.cloud.multishop.dto.ShopWithdrawCashDTO;
import com.jf.cloud.multishop.mapper.ShopWalletMapper;
import com.jf.cloud.multishop.mapper.ShopWalletZoneMapper;
import com.jf.cloud.multishop.mapper.ShopWithdrawCashMapper;
import com.jf.cloud.multishop.model.ShopWithdrawCash;
import com.jf.cloud.multishop.service.*;
import com.jf.cloud.multishop.vo.ShopBankCardVO;
import com.jf.cloud.multishop.vo.ShopWalletVO;
import com.jf.cloud.multishop.vo.ShopWithdrawCashLogExcelVO;
import com.jf.cloud.multishop.vo.ShopWithdrawCashVO;
import ma.glasnost.orika.MapperFacade;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 商家提现申请信息
 *
 * @author zz
 * @date 2021-01-07 16:41:03
 */
@Service
public class ShopWithdrawCashServiceImpl implements ShopWithdrawCashService {

    @Autowired
    private ShopWithdrawCashMapper shopWithdrawCashMapper;
    @Autowired
    private ShopDetailService shopDetailService;
    @Autowired
    private NotifyFeignClient sendNotifyFeignClient;
    @Autowired
    private AccountFeignClient accountFeignClient;
    @Autowired
    private ShopWalletService shopWalletService;
    @Autowired
    private ShopBankCardService shopBankCardService;
    @Autowired
    private ShopWalletMapper shopWalletMapper;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private RocketMQTemplate addShopWalletLogTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ShopWalletZoneService shopWalletZoneService;
    @Autowired
    private ShopWalletZoneMapper shopWalletZoneMapper;
    @Autowired
    private SegmentFeignClient segmentFeignClient;
    @Autowired
    private ConfigFeignClient configFeignClient;


    @Override
    public PageVO<ShopWithdrawCash> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> shopWithdrawCashMapper.list());
    }

    @Override
    public ShopWithdrawCash getByCashId(Long cashId) {
        return shopWithdrawCashMapper.getByCashId(cashId);
    }

    @Override
    public void save(ShopWithdrawCash shopWithdrawCash) {
        shopWithdrawCashMapper.save(shopWithdrawCash);
    }

    @Override
    public void update(ShopWithdrawCash shopWithdrawCash) {
        shopWithdrawCashMapper.update(shopWithdrawCash);
    }

    @Override
    public void deleteById(Long cashId) {
        shopWithdrawCashMapper.deleteById(cashId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void apply(ShopWithdrawCashDTO shopWithdrawCashDTO, Long shopId) {
        Date now = new Date();
        this.checkWithdrawInfo(shopWithdrawCashDTO, shopId);
        ShopWithdrawCash shopWithdrawCash = mapperFacade.map(shopWithdrawCashDTO, ShopWithdrawCash.class);
        shopWithdrawCash.setUpdateTime(now);
        shopWithdrawCash.setCreateTime(now);
        shopWithdrawCash.setStatus(ShopWithdrawCashStatus.WAITAUDIT.value());
        shopWithdrawCash.setShopId(shopId);
        this.checkConfig(shopWithdrawCash);
        ShopWalletVO shopWallet = shopWalletService.getByShopId(shopId);
        if (shopWithdrawCash.getAmount() > shopWallet.getSettledAmount()) {
            // 可提现金额不足
            throw new LuckException("可提现金额不足");
        }
        // 插入提现申请记录
        shopWithdrawCashMapper.save(shopWithdrawCash);

        // 更新钱包的结算金额和冻结金额
        updateShopWalletByApply(shopWithdrawCash);

        List<MongoShopWalletLogBO> shopWalletLogList = new ArrayList<>();
        // 保存提现记录
        this.saveShopWalletLog(shopWithdrawCash.getAmount(), shopId, now, ShopWalletChangeReason.APPLY_CASH, shopWalletLogList);

        // 用mq批量保存钱包日志
        SendStatus sendWalletLogStatus = addShopWalletLogTemplate.syncSend(RocketMqConstant.ADD_SHOP_WALLET_LOG_TOPIC, new GenericMessage<>(shopWalletLogList)).getSendStatus();
        if (!Objects.equals(sendWalletLogStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    /**
     * 更新钱包金额
     */
    private void updateShopWalletByApply(ShopWithdrawCash shopWithdrawCash) {
        Long shopId = shopWithdrawCash.getShopId();
        Long changeAmount = shopWithdrawCash.getAmount();
        if (Objects.equals(changeAmount, 0L)) {
            return;
        }
        // 1.增加冻结金额
        boolean addFreezeAmountSuccess = shopWalletMapper.addFreezeAmount(shopId, changeAmount) > 0;
        if (!addFreezeAmountSuccess) {
            throw new LuckException("提现申请发生异常");
        }
        // 2.减少结算金额
        RLock lock = redissonClient.getLock(LockCacheNames.LOCK_SHOP_WALLET_ZONE_PREFIX + shopId);
        try {
            lock.lock();
            boolean isSuccess = shopWalletService.subSettledAmount(shopId, changeAmount);
            if (!isSuccess) {
                throw new LuckException("可提现金额不足");
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public PageVO<ShopWithdrawCashVO> pageByParam(PageDTO pageDTO, ShopWithdrawCashDTO shopWithdrawCashDTO) {
        return PageUtil.doPage(pageDTO, () -> shopWithdrawCashMapper.listByParam(shopWithdrawCashDTO));
    }

    @Override
    public List<ShopWithdrawCashLogExcelVO> listShopWithdrawCashLogExcel(ShopWithdrawCashDTO shopWithdrawCashDTO) {
        return shopWithdrawCashMapper.listShopWithdrawCashLogExcel(shopWithdrawCashDTO);
    }

    @Override
    public ShopWithdrawCashVO getDetailByCashId(Long cashId) {
        return shopWithdrawCashMapper.getDetailByCashId(cashId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditWithdrawCash(ShopWithdrawCashDTO shopWithdrawCashDTO, Long userId) {
        ShopWithdrawCashVO shopWithdrawCashVO = shopWithdrawCashMapper.getDetailByCashId(shopWithdrawCashDTO.getCashId());
        if (Objects.isNull(shopWithdrawCashVO)) {
            throw new LuckException("未找到申请信息");
        }
        if (!Objects.equals(shopWithdrawCashVO.getStatus(), ShopWithdrawCashStatus.WAITAUDIT.value())) {
            throw new LuckException("该申请已被审核");
        }

        this.checkAuditInfo(shopWithdrawCashDTO, shopWithdrawCashDTO.getShopId());
        ShopWithdrawCash shopWithdrawCash = mapperFacade.map(shopWithdrawCashVO, ShopWithdrawCash.class);
        Date now = new Date();
        shopWithdrawCash.setAuditingTime(now);
        shopWithdrawCash.setAuditorId(userId);
        shopWithdrawCash.setStatus(shopWithdrawCashDTO.getStatus());
        shopWithdrawCash.setRemarks(shopWithdrawCashDTO.getRemarks());
        shopWithdrawCash.setPayingAccount(shopWithdrawCashDTO.getPayingAccount());
        shopWithdrawCash.setPayingTime(shopWithdrawCashDTO.getPayingTime());
        shopWithdrawCash.setPayingCardNo(shopWithdrawCashDTO.getPayingCardNo());
        Long shopId = shopWithdrawCash.getShopId();
        Long changeAmount = shopWithdrawCash.getAmount();

        ShopWalletChangeReason shopWalletChangeReason = ShopWalletChangeReason.PASS_CASH;
        List<MongoShopWalletLogBO> shopWalletLogList = new ArrayList<>();
        if (!Objects.equals(changeAmount, 0L)) {
            switch (Objects.requireNonNull(ShopWithdrawCashStatus.instance(shopWithdrawCash.getStatus()))) {
                case SUCCESSAUDIT:
                    // 审核通过, 减去冻结的金额
                    boolean subFreezeAmountSuccess = shopWalletMapper.subFreezeAmount(shopId, changeAmount) > 0;
                    if (!subFreezeAmountSuccess) {
                        throw new LuckException("冻结金额不足，提现失败");
                    }
                    break;
                case FAILAUDIT:
                    // 审核不通过，将冻结的金额返回到已结算金额
                    boolean subFreezeAmount = shopWalletMapper.subFreezeAmount(shopId, changeAmount) > 0;
                    if (!subFreezeAmount) {
                        throw new LuckException("审核失败，冻结金额异常");
                    }
                    RLock lock = redissonClient.getLock(LockCacheNames.LOCK_SHOP_WALLET_ZONE_PREFIX + shopId);
                    try {
                        lock.lock();
                        // 增加结算金额到主分片
                        boolean addSettledAmount = shopWalletMapper.addSettledAmount(shopId, changeAmount) > 0;
                        if (!addSettledAmount) {
                            throw new LuckException("审核失败，结算金额异常");
                        }
                    } finally {
                        lock.unlock();
                    }
                    shopWalletChangeReason = ShopWalletChangeReason.REFUSE_CASH;
                    break;
                default:
                    // 审核状态有误
                    throw new LuckException("审核状态有误");
            }
        }
        // 保存日志
        this.saveShopWalletLog(changeAmount, shopId, now, shopWalletChangeReason, shopWalletLogList);
        // 用mq批量保存钱包日志
        SendStatus sendWalletLogStatus = addShopWalletLogTemplate.syncSend(RocketMqConstant.ADD_SHOP_WALLET_LOG_TOPIC, new GenericMessage<>(shopWalletLogList)).getSendStatus();
        if (!Objects.equals(sendWalletLogStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }

        if (shopWithdrawCashMapper.updateByIdAndStatus(shopWithdrawCash, shopWithdrawCash.getCashId(), ShopWithdrawCashStatus.WAITAUDIT.value()) != 1) {
            throw new LuckException("该申请已被审核");
        }
    }

    private void checkConfig(ShopWithdrawCash shopWithdrawCash) {
        ShopWithdrawCashConfigDto config = getConfig();
        if (shopWithdrawCash.getAmount() < config.getWithdrawCashLeast()) {
            throw new LuckException("提现金额过少，最低提现:" + config.getWithdrawCashLeast() / 100);
        }
        if (Objects.nonNull(config.getWithdrawCashMax()) && shopWithdrawCash.getAmount() > config.getWithdrawCashMax()) {
            throw new LuckException("提现金额过大,最高提现:" + config.getWithdrawCashMax() / 100);
        }
        Integer frequency = config.getFrequency();
        Date startTimes;
        Date endTimes;
        String date;
        if (frequency.equals(WithdrawCashType.WEEKEND.value())) {
            startTimes = DateUtils.getCurrentWeekStartTimes(new Date());
            endTimes = DateUtils.getCurrentWeekEndTimes(new Date());
            date = WithdrawCashType.WEEKEND.date();
        } else if (frequency.equals(WithdrawCashType.MONTH.value())) {
            startTimes = DateUtils.getMonthFirstOrLastDay(new Date(), 0);
            endTimes = DateUtils.getMonthFirstOrLastDay(new Date(), 1);
            date = WithdrawCashType.MONTH.date();
        } else {
            startTimes = DateUtils.getCurrentYearStartTime(new Date());
            endTimes = DateUtils.getCurrentYearEndTime(new Date());
            date = WithdrawCashType.YEAR.date();
        }
        int count = shopWithdrawCashMapper.getWithdrawCashCount(startTimes, endTimes, shopWithdrawCash);
        if (count + 1 > config.getNumber()) {
            throw new LuckException("超出提现频率限制,当前频率为:" + date + " " + config.getNumber() + "次");
        }
    }


    @Override
    public void saveConfig(ShopWithdrawCashConfigDto shopWithdrawCashDto) {
        checkParam(shopWithdrawCashDto);
        SysConfigApiVO sysConfigApiVO = new SysConfigApiVO();
        sysConfigApiVO.setParamKey(Constant.SHOP_WITHDRWA);
        sysConfigApiVO.setRemark("商家提现配置");
        sysConfigApiVO.setParamValue(Json.toJsonString(shopWithdrawCashDto));
        configFeignClient.saveOrUpdateSysConfig(sysConfigApiVO);
    }

    @Override
    public ShopWithdrawCashConfigDto getConfig() {

        ShopWithdrawCashConfigDto shopWithdrawCashConfigDto = Json.parseObject(configFeignClient.getConfig(Constant.SHOP_WITHDRWA).getData(), ShopWithdrawCashConfigDto.class);
        if (Objects.isNull(shopWithdrawCashConfigDto)) {
            shopWithdrawCashConfigDto = new ShopWithdrawCashConfigDto();
            shopWithdrawCashConfigDto.setWithdrawCashLeast(100D);
            shopWithdrawCashConfigDto.setFrequency(1);
            shopWithdrawCashConfigDto.setNumber(1);
        }
        return shopWithdrawCashConfigDto;
    }

    private void checkParam(ShopWithdrawCashConfigDto shopWithdrawCashDto) {
        Double least = shopWithdrawCashDto.getWithdrawCashLeast();
        Double max = shopWithdrawCashDto.getWithdrawCashMax();
        Integer frequency = shopWithdrawCashDto.getFrequency();
        Integer number = shopWithdrawCashDto.getNumber();
        if (Objects.isNull(least) || least < 1) {
            throw new LuckException("最少金额设置错误");
        }
        if (Objects.nonNull(max) && max < least) {
            throw new LuckException("最大金额设置错误");
        }
        if (Objects.isNull(frequency) || (!frequency.equals(WithdrawCashType.WEEKEND.value()) && !frequency.equals(WithdrawCashType.MONTH.value()) && !frequency.equals(WithdrawCashType.YEAR.value()))) {
            throw new LuckException("提现频率设置错误");
        }
        if (Objects.isNull(number) || number < 1) {
            throw new LuckException("提现次数设置错误");
        }
    }

    private void saveShopWalletLog(Long changeAmount, Long shopId, Date now, ShopWalletChangeReason shopWalletChangeReason, List<MongoShopWalletLogBO> shopWalletLogList) {

        // 插入结算金额修改记录【提现申请/提现申请被拒绝】
        if (Objects.equals(shopWalletChangeReason, ShopWalletChangeReason.APPLY_CASH) || Objects.equals(shopWalletChangeReason, ShopWalletChangeReason.REFUSE_CASH)) {
            MongoShopWalletLogBO shopWalletLog = new MongoShopWalletLogBO();
            ServerResponseEntity<Long> response = segmentFeignClient.getSegmentId(DistributedIdKey.MALL4CLOUD_SHOP_WALLET_LOG);
            if (!response.isSuccess()) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
            shopWalletLog.setWalletLogId(response.getData());
            shopWalletLog.setCreateTime(now);
            shopWalletLog.setUpdateTime(now);
            shopWalletLog.setShopId(shopId);
            shopWalletLog.setReason(shopWalletChangeReason.value());
            shopWalletLog.setShopChangeAmount(Math.abs(changeAmount));
            shopWalletLog.setShopIoType(Objects.equals(shopWalletChangeReason, ShopWalletChangeReason.APPLY_CASH) ? ShopWalletIoType.EXPENDITURE.value() : ShopWalletIoType.INCOME.value());
            shopWalletLog.setAmountType(ShopWalletAmountType.SETTLED_AMOUNT.value());
            shopWalletLogList.add(shopWalletLog);
        }

        // 插入冻结金额修改记录
        MongoShopWalletLogBO shopWalletLog = new MongoShopWalletLogBO();
        ServerResponseEntity<Long> response = segmentFeignClient.getSegmentId(DistributedIdKey.MALL4CLOUD_SHOP_WALLET_LOG);
        if (!response.isSuccess()) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
        shopWalletLog.setWalletLogId(response.getData());
        shopWalletLog.setCreateTime(now);
        shopWalletLog.setUpdateTime(now);
        shopWalletLog.setShopId(shopId);
        shopWalletLog.setReason(shopWalletChangeReason.value());
        shopWalletLog.setShopChangeAmount(Math.abs(changeAmount));
        shopWalletLog.setShopIoType(!Objects.equals(shopWalletChangeReason, ShopWalletChangeReason.APPLY_CASH) ? ShopWalletIoType.EXPENDITURE.value() : ShopWalletIoType.INCOME.value());
        shopWalletLog.setAmountType(ShopWalletAmountType.FREEZE_AMOUNT.value());
        shopWalletLogList.add(shopWalletLog);
    }

    private void checkWithdrawInfo(ShopWithdrawCashDTO shopWithdrawCashDTO, Long shopId) {
        Integer sysType = AuthUserContext.get().getSysType();
        ServerResponseEntity<AuthAccountVO> merchantInfoRes = accountFeignClient.getAccountInfoByTenantId(shopId, sysType);
        if (!merchantInfoRes.isSuccess() || Objects.isNull(merchantInfoRes.getData())) {
            throw new LuckException("商家信息获取出错");
        }
        if (shopWithdrawCashDTO.getAmount() < 1) {
            throw new LuckException("提现金额不能少于1.00元");
        }
        ServerResponseEntity<Boolean> sendNotifyResponse = sendNotifyFeignClient.checkValidCode(merchantInfoRes.getData().getPhone(), shopWithdrawCashDTO.getValidCode(), SendTypeEnum.VALID);
        if (!sendNotifyResponse.isSuccess() || !sendNotifyResponse.getData()) {
            throw new LuckException("验证码有误或已过期");
        }
        ShopDetailVO shopDetailVO = shopDetailService.getByShopId(shopId);
        if (Objects.isNull(shopDetailVO)) {
            throw new LuckException("店铺不存在");
        }

        ShopBankCardVO shopBankCardVO = shopBankCardService.getByShopBankCardId(shopWithdrawCashDTO.getShopBankCardId());
        if (Objects.isNull(shopBankCardVO) || !Objects.equals(shopBankCardVO.getShopId(), shopId)) {
            throw new LuckException("银行卡信息错误");
        }
    }

    private void checkAuditInfo(ShopWithdrawCashDTO shopWithdrawCashDTO, Long shopId) {
        ShopDetailVO shopDetailVO = shopDetailService.getByShopId(shopId);
        if (Objects.isNull(shopDetailVO)) {
            throw new LuckException("店铺不存在");
        }
        ShopBankCardVO shopBankCard = shopBankCardService.getByShopBankCardId(shopWithdrawCashDTO.getShopBankCardId());
        if (Objects.isNull(shopBankCard) || !Objects.equals(shopBankCard.getShopId(), shopId)) {
            throw new LuckException("银行卡信息错误");
        }
    }

    @Override
    public void checkDrawCashAmount(Long amount) {
        // 基础数值检查
        // 基础数值检查 - 小于100
        if (amount < Constant.HUNDRED) {
            throw new LuckException("提现金额不能少于1.00元");
        }
        // 系统配置检查
        ShopWithdrawCash shopWithdrawCash = new ShopWithdrawCash();
        shopWithdrawCash.setAmount(amount);
        this.checkConfig(shopWithdrawCash);
    }
}
